package sub

import (
	"bufio"
	"context"

	// "crypto/internal/edwards25519/field"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"os/exec"
	"path/filepath"

	"math"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/reflection"
	"google.golang.org/grpc/status"

	// "github.com/pkg/errors"
	"regexp"
	"strconv"
	"strings"
	"time"

	pb "a.local/generated_pb" // import ./util as u
	"golang.org/x/crypto/ssh"

	"github.com/shirou/gopsutil/v3/cpu"
	"github.com/shirou/gopsutil/v3/disk"
	"github.com/shirou/gopsutil/v3/mem"
	snet "github.com/shirou/gopsutil/v3/net"
	"github.com/shirou/gopsutil/v3/process"

	"github.com/jackpal/gateway" // 🦜 : for DiscoverGateway()
)

var (
	myIp string
	dryRun bool
	safety int
	mockMore bool
	cwd string
)

type server struct {
	pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	log.Printf("Received: %v", in.GetName())
	return &pb.HelloReply{Msg : "Hello, " + in.GetName()}, nil
}

func mock_DescSafeboxRpl() *pb.DescSafeboxRpl {
	x := &pb.DescSafeboxRpl{
		// Files: []*pb.SimpleFileInfo{
		// 	{
		// 		FileName: "aaa.txt", Size: 1000,
		// 		LastModifyTime: "2000-01-01 12:00:00",
		// 		LastAccessTime: "2222-12-31 12:00:00",
		// 		Permissions: "666",
		// 	},
		// 	{
		// 		FileName: "bbb.txt", Size: 100,
		// 		LastModifyTime: "2000-01-02 12:00:00",
		// 		LastAccessTime: "2222-12-30 12:00:00",
		// 		Permissions: "666",
		// 	},
		// 	{
		// 		FileName: "ccc.txt", Size: 10,
		// 		LastModifyTime: "2000-01-03 12:00:00",
		// 		LastAccessTime: "2222-12-29 12:00:00",
		// 		Permissions: "666",
		// 	},
		// 	// {FileName: "bbb.txt", Size: "123K", LastUpdate: "2023-11-27 12:00:00"},
		// },
	}

	// Add 10 more elements using a for loop
	for i := 0; i < 20; i++ {
		newFile := &pb.SimpleFileInfo{
			FileName:       fmt.Sprintf("file%d.txt", i+1),
			Size:           uint64(50 + i*10),
			LastModifyTime: "2000-01-04 12:00:00",
			LastAccessTime: "2222-12-28 12:00:00",
			Permissions:    "666",
		}
		x.Files = append(x.Files, newFile)
	}
	return x
}

func makeSshClientAndSession(user string, pswd string) (*ssh.Client, *ssh.Session, error) {
	sshConfig := &ssh.ClientConfig{
		User: user,
		Auth: []ssh.AuthMethod{
			ssh.Password(pswd),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		ClientVersion:   "",
		Timeout:         10 * time.Second,
	}

	//建立与SSH服务器的连接
	sshClient, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s",myIp, "22"), sshConfig)
	if err != nil {
		msg := fmt.Sprintf("建立与SSH服务器的连接错误: %v", err)
		return nil, nil, status.Error(codes.Internal, msg)
	}
	// defer sshClient.Close()

	// 建立新会话
	session, _ := sshClient.NewSession()
	// defer session.Close()

	return sshClient, session, nil
}

/*
   Parse the output returned by `find`:

     ./hi.txt <last-modified-time> <last-access-time> <size> <mode>
 */
func parseSimpleFileInfo(line string) (*pb.SimpleFileInfo, error) {

	fields := strings.Split(line, " ")
	resp := pb.SimpleFileInfo{
		FileName: fields[0],
		Permissions: fields[4],
	}

	timeSecFlt, err := strconv.ParseFloat(fields[1], 64)
	if err != nil {
		return nil , err
	}
	timeSec := int64(math.Round(timeSecFlt))
	resTime := time.Unix(timeSec, 0)
	resp.LastModifyTime = resTime.Format("2006-01-02 15:04:05")

	timeSecFlt, err = strconv.ParseFloat(fields[2], 64)
	if err != nil {
		return nil , err
	}
	timeSec = int64(math.Round(timeSecFlt))
	resTime = time.Unix(timeSec, 0)
	resp.LastAccessTime = resTime.Format("2006-01-02 15:04:05")

	var s uint64
	s, err = strconv.ParseUint(fields[3], 10, 64)
	if err != nil {
		return nil , err
	}

	resp.Size = s
	return &resp, nil
}

func (s *server) DescSafebox(ctx context.Context, in *pb.PswdLogin ) (*pb.DescSafeboxRpl, error) {
	if dryRun {
		if mockMore {
			return mock_DescSafeboxRpl(), nil
		}

		return &pb.DescSafeboxRpl{
			Files: []*pb.SimpleFileInfo{
				{
					FileName: "aaa.txt", Size: 1000,
					LastModifyTime: "2000-01-01 12:00:00",
					LastAccessTime: "2222-12-31 12:00:00",
					Permissions: "666",
				},
				{
					FileName: "bbb.txt", Size: 100,
					LastModifyTime: "2000-01-02 12:00:00",
					LastAccessTime: "2222-12-30 12:00:00",
					Permissions: "666",
				},
				{
					FileName: "ccc.txt", Size: 10,
					LastModifyTime: "2000-01-03 12:00:00",
					LastAccessTime: "2222-12-29 12:00:00",
					Permissions: "666",
				},
				// {FileName: "bbb.txt", Size: "123K", LastUpdate: "2023-11-27 12:00:00"},
			},
		},nil

	}
	// 通过SSH服务进行shell脚本执行

	sshClient, session, err := makeSshClientAndSession(in.GetUsr(), in.GetPswd())
	if err != nil {
		return nil, err
	}
	defer sshClient.Close()
	defer session.Close()

	var output []byte
	// output, err = session.Output("find ~/Private -type f -exec stat -c \"%Y %n\" {} \\; | awk '{print $2,$1}'")
	output, err = session.Output("find ~/Private -type f -printf '%p %T@ %A@ %s %m\n'")
	/*
	   ./hi.txt <last-modified-time> <last-access-time> <size> <mode>
	 */

	if err != nil {
		msg := fmt.Sprintf("获取私密目录下文件信息-执行脚本错误: %v", err)
		return nil, status.Error(codes.Internal, msg)
	}

	// 解析输出，获取当前正在运行的应用
	lines := strings.Split(string(output), "\n")
	r := pb.DescSafeboxRpl{}

	for i := 0; i < len(lines)-1; i++ {
		resp, err := parseSimpleFileInfo(lines[i])
		if err != nil {
			msg := fmt.Sprintf("获取私密目录下文件信息-解析输出错误: %v", err)
			return nil, status.Error(codes.Internal, msg)
		}
		r.Files = append(r.Files, resp)
	}

	return &r, nil
}

func (s *server) CreateSafebox(ctx context.Context, in *pb.PswdLogin ) (*pb.HelloReply, error) {
	if dryRun {
		return &pb.HelloReply{Msg: "[mock]创建用户安全箱成功在 ~/Private"}, nil
	}

	dir, err := os.Getwd()
	if err != nil {		// unlikely
		msg := fmt.Sprintf("获取当前目录错误: %v", err)
		return nil, status.Error(codes.Internal, msg)
	}

	// 通过SSH服务进行shell脚本执行
	sshClient, session, err := makeSshClientAndSession(in.GetUsr(), in.GetPswd())
	if err != nil {
		return nil, err
	}
	defer sshClient.Close()
	defer session.Close()

	var output []byte
	output, err = session.CombinedOutput(fmt.Sprintf("expect %s/scripts/create_ecryptfs.sh %s", dir, in.GetPswd()))
	if err != nil {
		msg := fmt.Sprintf("创建用户安全箱失败-执行脚本错误: %v", err)
		return nil, status.Error(codes.Internal, msg)
	}

	// 错误判断
	if strings.Contains(string(output), "ERROR") {
		if strings.Contains(string(output), "already exists") {
			msg := fmt.Sprintf("用户安全箱已存在，每个用户只能做一个: %v", err)
			return nil, status.Error(codes.AlreadyExists, msg)
		}
		msg := fmt.Sprintf("创建用户安全箱-执行脚本结果输出错误: %v", err)
		return nil, status.Error(codes.Internal, msg)
	}

	// resp
	return &pb.HelloReply{Msg: "创建用户安全箱成功在 ~/Private"}, nil
}

func (s *server) ChangeRootPswd(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply,error){
	if dryRun {
		return &pb.HelloReply{Msg: "[mock]更改服务器root用户密码修改为: " + in.GetName()}, nil
	}

	// 请求参数判断
	pswd := in.GetName()
	if ok := verifyPassword(8, 16, pswd); !ok {
		msg := "更改服务器root用户密码错误: 必须包含数字、大写字母、小写字母、特殊字符(如.@$!*#_~?&^)至少3种的组合且长度在8-16之间。"
		return nil, status.Error(codes.InvalidArgument, msg)
	}


	cmd := exec.Command("sudo", "chpasswd")
	stdin, err := cmd.StdinPipe()

	if err != nil {
		msg := fmt.Sprintf("更改服务器root用户密码错误-cmd.StdinPipe()错误 %v", err)
		return nil, status.Error(codes.Internal, msg)
	}

	go func() {
		defer stdin.Close()
		io.WriteString(stdin, fmt.Sprintf("root:%s\n", pswd))
	}()

	err = cmd.Run()
	if err != nil {
		msg := fmt.Sprintf("更改服务器root用户密码错误-cmd.Run()错误 %v", err)
		return nil, status.Error(codes.Internal, msg)
	}

	return &pb.HelloReply{Msg: "更改服务器root用户密码修改为: " + pswd}, nil
}

func parseLineIntoUserInfo(line string) (*pb.UserInfo, error) {
	fields := strings.Split(line, ":")
	if len(fields) != 7 {
		return nil, fmt.Errorf("line format error: %s", line)
	}

	uid := fields[2]
	name := fields[0]
	homeDir := fields[5]
	description := fields[4]
	shell := fields[6]

	return &pb.UserInfo{
		Uid: uid, Name: name,
		HomeDir: homeDir,
		Description: description,
		Shell: shell,
	}, nil
}

func GetUsers0() (*pb.GetUsersRpl, error) {
	/*
	   🦜 : We took this out because we kinda need this in getBashHistory()
	*/
	cmd := exec.Command("getent", "passwd")
	output, err := cmd.Output()
	if err != nil {
		msg := fmt.Sprintf("获取用户列表错误: %v", err)
		return nil, status.Error(codes.Internal, msg)
	}

	lines := strings.Split(string(output), "\n")
	r := pb.GetUsersRpl{}
	for  _, line := range lines {
		if len(line) == 0 {
			continue
		}
		u, err := parseLineIntoUserInfo(line)
		if err != nil {
			continue
		}
		r.Users = append(r.Users, u)
	}
	return &r, nil
}

func (s *server) GetUsers(ctx context.Context, in *pb.Empty) (*pb.GetUsersRpl, error) {
	// 🦜 : Feels harmless, so not checking
	// if dryRun {
	// 	return &pb.GetUsersRpl{
	// 		Users: []*pb.UserInfo{
	// 			{
	// 				Uid: "123", Name: "me",
	// 				HomeDir: "/home/me",
	// 				Description: "A normal user",
	// 				Shell: "/bin/bash",
	// 			},
	// 			{
	// 				Uid: "456", Name: "aaa",
	// 				HomeDir: "/home/aaa",
	// 				Description: "A normal user",
	// 				Shell: "/bin/bash",
	// 			},
	// 		},
	// 	}, nil
	// }

	return GetUsers0()
}

func parseLineIntoLoginInfo(l string) (*pb.LoginInfo, error) {
	/*
	   🦜 : Example lines:

	   0        1            2                3                         4 5                           6
	   me       tty2         tty2             2023-12-25T10:11:28+08:00 - down                       (09:08)
	   reboot   system boot  6.2.0-39-generic 2023-12-25T10:11:07+08:00 - 2023-12-25T19:19:44+08:00  (09:08)

	*/
	if len(l) == 0 {
		return nil, fmt.Errorf("empty line")
	}

	f := strings.Fields(l)
	// if it starts with `reboot`,`system`,`boot`, concat fields 1,2 (0-based)
	if f[0] == "reboot" && f[1] == "system" && f[2] == "boot" {
		f[2] = "system boot"
		f[1] = f[0]
		f = f[1:]
	}

	if len(f) != 7 {
		return nil, fmt.Errorf("line format error, expect 7 fields: %s", l)
	}

	// parse them
	user := f[0]
	terminal := f[1]
	loginIp := f[2]

	// theirLayout := "2006-01-02T15:04:05-07:00"
	/*
	   🐢 : In go, both "-07:00" and "Z07:00" can parse the time zone
	   "+08:00", they are synonym.
	 */

	loginTime, err := time.Parse( time.RFC3339 , f[3])
	if err != nil {
		return nil, err
	}

	humanLayout := "2006-01-02 15:04:05"
	loginTimeStr := loginTime.Format(humanLayout)

	// field 5 can have special values: `down`, `crash`
	logoutTime := f[5]
	var logoutTimeStr string
	if logoutTime == "down" || logoutTime == "crash" {
		logoutTimeStr = logoutTime
	} else {
		logoutTime, err := time.Parse(time.RFC3339, f[5])
		if err != nil {
			return nil, err
		}
		logoutTimeStr = logoutTime.Format(humanLayout)
	}

	return &pb.LoginInfo{
		User: user,
		Terminal: terminal,
		LoginIp: loginIp,
		LoginTime: loginTimeStr,
		LogoutTime: logoutTimeStr,
	}, nil

}

func (s *server) GetLoginHistory(ctx context.Context, in *pb.GetLoginHistoryReq) (*pb.GetLoginHistoryRpl, error) {

	// 🦜 : Feels harmless, so not checking
	// if dryRun {
	// 	/*
	// 	   message LoginInfo {
	// 		string user = 1;
	// 		string terminal = 2;
	// 		string loginIp = 3;
	// 		string loginTime = 4;
	// 		string logoutTime = 5;
        //            }

	// 	   message GetLoginHistoryRpl {
	// 	            repeated LoginInfo loginInfos = 1;
	// 	   }
	// 	 */
	// 	return &pb.GetLoginHistoryRpl{
	// 		LoginInfos: []*pb.LoginInfo{
	// 			{User: "me", Terminal: "tty2", LoginIp: "tty2", LoginTime: "2021-01-01 12:00:00", LogoutTime: "2021-01-01 22:00:00",},
	// 			{User: "reboot", Terminal: "system boot", LoginIp: "6.2.0-39-generic", LoginTime: "2021-01-01 12:00:00", LogoutTime: "2021-01-01 22:00:00",},
	// 			{User: "me", Terminal: "tty2", LoginIp: "tty2", LoginTime: "2022-01-01 12:00:00", LogoutTime: "2022-01-01 22:00:00",},
	// 			{User: "me", Terminal: "tty2", LoginIp: "tty2", LoginTime: "2023-01-01 12:00:00", LogoutTime: "2023-01-01 22:00:00",},
	// 		},
	// 	}, nil
	// }

	/*
	   🐢 : Do something like:

	last --since '2023-12-25 09:00:00' --until '2023-12-28 09:00:00' --time-format iso <name>

	   so passed in date should already be acceptable by `last`
	 */

	var cmd *exec.Cmd
	if in.GetUser() == "" {
		cmd = exec.Command("last", "--since", in.GetSinceTime(), "--until", in.GetUntilTime(), "--time-format", "iso")
	} else {
		cmd = exec.Command("last","--since", in.GetSinceTime(), "--until", in.GetUntilTime(), "--time-format", "iso", in.GetUser())
	}
	output, err := cmd.Output()
	if err != nil {
		msg := fmt.Sprintf("获取用户登录历史错误: %v", err)
		return nil, status.Error(codes.Internal, msg)
	}

	lines := strings.Split(string(output), "\n")
	r := pb.GetLoginHistoryRpl{}
	for  _, line := range lines {
		u, err := parseLineIntoLoginInfo(line)
		if err != nil {
			continue
		}
		r.LoginInfos = append(r.LoginInfos, u)
	}

	return &r, nil
}

/*

   message GetNetstatsInfoRpl {
  repeated ConnectionStat connectionStats = 2;
  // ^^ things you got from netstats
  }

  rpc GetNetstatsInfo (Empty) returns (GetNetstatsInfoRpl) {}
*/

func (s *server) GetNetstatsInfo(ctx context.Context, in *pb.Empty) (*pb.GetNetstatsInfoRpl, error) {
	r := &pb.GetNetstatsInfoRpl{}
	r.ConnectionStats = make([]*pb.ConnectionStat, 0)

	// 2. get the connection stats
	connStats, err := snet.Connections("inet")
	if err != nil {
		msg := fmt.Sprintf("获取网络信息错误: %v", err)
		return nil, status.Error(codes.Internal, msg)
	}


	// go -> pb
	for _, connStat := range connStats {
		localAddrPb := &pb.AddrPort{
			Addr: connStat.Laddr.IP,
			Port: connStat.Laddr.Port,
		}

		remoteAddrPb := &pb.AddrPort{
			Addr: connStat.Raddr.IP,
			Port: connStat.Raddr.Port,
		}

		connStatPb := &pb.ConnectionStat{
			Fd: connStat.Fd,
			Family: connStat.Family,
			Type: connStat.Type,
			LocalAddr: localAddrPb,
			RemoteAddr: remoteAddrPb,
			Status: connStat.Status,
			Uids: connStat.Uids,
			Pid: connStat.Pid,
		}

		// [2024-01-26] 🦜 : Get the exeName using pid
		p := process.Process{Pid: int32(connStat.Pid)}
		exeName, err := p.Name()
		if err != nil {
			// log.Printf("获取网络信息错误: %v", err)
		}else{
			connStatPb.ExeName = exeName
		}

		r.ConnectionStats = append(r.ConnectionStats, connStatPb)
	}
	return r, nil
}


/*
   🦜 : This is a big function, the returned struct is big, but in fact it just
	wrap the pkg "github.com/shirou/gopsutil/v3/net"
 */
func (s *server) GetNICInfo(ctx context.Context, in *pb.Empty) (*pb.GetNICInfoRpl, error) {
	r := &pb.GetNICInfoRpl{}
	// return &r, nil

	r.InterfaceStats = make([]*pb.InterfaceStat, 0)

	// 1. get the interface stats

	// 1.1 get the basic stats
	ifaces, err := snet.Interfaces()
	if err != nil {
		msg := fmt.Sprintf("获取网络信息错误: %v", err)
		return nil, status.Error(codes.Internal, msg)
	}

	// 1.2 get the IO stats
	var ioStats []snet.IOCountersStat
	ioStats, err = snet.IOCounters(true)
	if err != nil {
		msg := fmt.Sprintf("获取网络信息错误: %v", err)
		return nil, status.Error(codes.Internal, msg)
	}

	// 1.3 make the IO stats indexed by interface name
	m := make(map[string]*pb.IOCounterStat)
	for _, ioStat := range ioStats {

		m[ioStat.Name] = &pb.IOCounterStat{
			BytesSent: ioStat.BytesSent,
			BytesRecv: ioStat.BytesRecv,
			PacketsSent: ioStat.PacketsSent,
			PacketsRecv: ioStat.PacketsRecv,
			Errin: ioStat.Errin,
			Errout: ioStat.Errout,
			Dropin: ioStat.Dropin,
			Dropout: ioStat.Dropout,
		}

	}


	// 1.40 prepare the gateway info
	defaultGateway, err1 := gateway.DiscoverGateway()
	defaultGatewayInterface, err2 := gateway.DiscoverInterface()
	// 1.4 make the InterfaceStat
	for _, iface := range ifaces {
		var flags []string
		var addrs []string
		var ifacePb pb.InterfaceStat

		for _, flag := range iface.Flags {
			flags = append(flags, flag)
		}
		for _, addr := range iface.Addrs {
			addrs = append(addrs, addr.Addr)
		}

		ifacePb = pb.InterfaceStat{
			Index: int32(iface.Index),
			Mtu: int32(iface.MTU),
			Name: iface.Name,
			HardwareAddr: iface.HardwareAddr,
			Flags: flags,
			Addrs: addrs,
			IoCounterStat: m[iface.Name],
		}

		if err1 == nil && err2 == nil {
			setDefaultGatewayMaybe(&ifacePb, defaultGateway, defaultGatewayInterface)
		}
		r.InterfaceStats = append(r.InterfaceStats, &ifacePb)
	}


	return r, nil
}

func setDefaultGatewayMaybe(ifacePb *pb.InterfaceStat, defaultGateway net.IP, defaultGatewayInterface net.IP) {
	// get the default gateway and set it if ifacePb.addrs contains a string that contains defaultGatewayInterface
	for _, addr := range ifacePb.Addrs {
		// fmt.Printf("🦜 :Checking if %s contains %s\n", addr, defaultGatewayInterface.String())
		if strings.Contains(addr, defaultGatewayInterface.String()) {
			ifacePb.DefaultGateway = defaultGateway.String()
			return
		}
	}
}

// VerifyPassword 密码校验规则: 必须包含数字、大写字母、小写字母、特殊字符(如.@$!%*#_~?&^)至少3种的组合且长度在8-16之间
func verifyPassword(minLength, maxLength int, pwd string) bool {
	if len(pwd) < minLength || len(pwd) > maxLength {
		return false
	}
	// 过滤掉这四类字符以外的密码串,直接判断不合法
	re, err := regexp.Compile(`^[a-zA-Z0-9.@$!%*#_~?&^]{8,16}$`)
	if err != nil {
		return false
	}
	match := re.MatchString(pwd)
	if !match {
		return false
	}

	var level = 0
	patternList := []string{`[0-9]+`, `[a-z]+`, `[A-Z]+`, `[.@$!%*#_~?&^]+`}
	for _, pattern := range patternList {
		match, _ := regexp.MatchString(pattern, pwd)
		if match {
			level++
		}
	}

	// if level < 3 {
	// 	return false
	// }
	return level >= 3

	return true
}

// rpc GetResourceInfo (Empty) returns (GetResourceInfoRpl) {}
func (s *server) GetResourceInfo(ctx context.Context, in *pb.Empty) (*pb.GetResourceInfoRpl, error) {

	/*

	   message GetResourceInfoRpl {
  string cpuPercent = 1;
  repeated CpuInfo cpuInfos = 2;
  MemoryInfo memoryInfo = 3;
  DiskInfo diskInfo = 4;
  map<string, IOCountersInfo> ioCountersInfo = 5;
  }
	 */
	r := &pb.GetResourceInfoRpl{}
	h , err := os.Hostname()
	if err != nil {
		msg := fmt.Sprintf("获取主机名错误: %v", err)
		return nil, status.Error(codes.Internal, msg)
	}
	r.Hostname = h

	// 获取CPU负载
	cpuLoad, err := cpu.Percent(time.Second, false /* percpu*/)
	//                          ^^^ usage per CPU or total usage
	if err != nil {
		msg := fmt.Sprintf("获取CPU负载错误: %v", err)
		return nil, status.Error(codes.Internal, msg)
	}
	r.CpuPercent = cpuLoad[0]

	cpuInfo, err := cpu.Info()
	if err != nil {
		msg := fmt.Sprintf("获取CPU信息错误: %v", err)
		return nil, status.Error(codes.Internal, msg)
	}
	r.CpuInfos = ConvertCpuInfo(cpuInfo)

	// --------------------------------------------------
	memUsage, err := mem.VirtualMemory()
	if err != nil {
		msg := fmt.Sprintf("获取内存信息错误: %v", err)
		return nil, status.Error(codes.Internal, msg)
	}

	r.MemoryInfo = ConvertMemInfo(memUsage)
	// --------------------------------------------------
	diskUsage, err := disk.Usage("/")
	if err != nil {
		msg := fmt.Sprintf("获取磁盘信息错误: %v", err)
		return nil, status.Error(codes.Internal, msg)
	}
	r.DiskInfo = ConvertDiskInfo(diskUsage)

	// --------------------------------------------------
	diskIOCounters, err := disk.IOCounters()
	if err != nil {
		msg := fmt.Sprintf("获取磁盘IO信息错误: %v", err)
		return nil, status.Error(codes.Internal, msg)
	}
	r.IoCountersInfo = ConvertIOCountersInfo(diskIOCounters)

	return r, nil
}

func ConvertCpuInfo(cpuInfo []cpu.InfoStat) []*pb.CpuInfo {
	var r []*pb.CpuInfo
	for _, info := range cpuInfo {
		r = append(r, &pb.CpuInfo{
			Cpu: int32(info.CPU),
			VendorId: info.VendorID,
			Family: info.Family,
			Model: info.Model,
			ModelName: info.ModelName,
			Stepping: int32(info.Stepping),
			Mhz: info.Mhz,
			CacheSize: int32(info.CacheSize),
			PhysicalId: info.PhysicalID,
			CoreId: info.CoreID,
			Cores: int32(info.Cores),
		})
	}
	return r
}

func ConvertMemInfo(m *mem.VirtualMemoryStat) *pb.MemoryInfo {
	return &pb.MemoryInfo{
		Total: m.Total,
		Available: m.Available,
		Used: m.Used,
		UsedPercent: m.UsedPercent,
	}
}

func ConvertDiskInfo(d *disk.UsageStat) *pb.DiskInfo {
	return &pb.DiskInfo{
		Path: d.Path,
		Fstype: d.Fstype,
		Total: d.Total,
		Free: d.Free,
		Used: d.Used,
		UsedPercent: d.UsedPercent,
		InodesTotal: d.InodesTotal,
		InodesUsed: d.InodesUsed,
		InodesFree: d.InodesFree,
		InodesUsedPercent: d.InodesUsedPercent,
	}
}

func ConvertIOCountersInfo(ioCounters map[string]disk.IOCountersStat) map[string]*pb.IOCountersInfo {
	r := make(map[string]*pb.IOCountersInfo)
	for k, v := range ioCounters {
		r[k] = &pb.IOCountersInfo{
			ReadCount: v.ReadCount,
			MergedReadCount: v.MergedReadCount,
			WriteCount: v.WriteCount,
			MergedWriteCount: v.MergedWriteCount,
			ReadBytes: v.ReadBytes,
			WriteBytes: v.WriteBytes,
			ReadTime: v.ReadTime,
			WriteTime: v.WriteTime,
			IopsInProgress: v.IopsInProgress,
			IoTime: v.IoTime,
			WeightedIO: v.WeightedIO,
			Name: v.Name,
			SerialNumber: v.SerialNumber,
			Label: v.Label,
		}
	}
	return r
}

func tryAddHisOrHerEntries(u *pb.UserInfo, r *pb.GetBashHistoryRpl) (*pb.GetBashHistoryRpl, error) {
	// 1. if there's a .bash_history in their HOME, get it. If there's none, just return
	historyFile := filepath.Join(u.HomeDir, ".bash_history")
	_, err := os.Stat(historyFile)
	if err != nil {	// file does not exist
		return r, nil
	}

	/*
	   🦜 : How to parse this?

	   🐢 : The file consists of many "entries". An entry can be either one
	   line or two lines.

	   One-line entry contains only the command, like:
	                sudo --help

	   Two-line entry contains first the #timestamp and then the command, like:
			#1704962500
			sudo -u keystone bash -c 'echo hi'

	   🦜 : So we can just keep parsing until we see the end of the file, right?

	   🐢 : Yeah, so index-based for loop is good enough.
	 */

	// 2. get the file
	cmd := exec.Command("bash", "-c", fmt.Sprintf("tail -n %d %s", 500, historyFile)) // 🦜 : 500 lines max
	output, _ := cmd.Output()

	// 将输出转换为字符串并分割行
	lines := strings.Split(string(output), "\n")

	// 3. parse the file
	for i := 0; i < len(lines)-1;{
		if strings.HasPrefix(lines[i], "#") && i+1 < len(lines) {
			/*
			   message BashHistoryEntry {string cmd = 1; string time = 2; string user = 3;}
			*/

			e := &pb.BashHistoryEntry{}
			// parse a two-line entry
			s := strings.TrimLeft(lines[i], "#")
			timestamp, err := strconv.ParseInt(s, 10, 64)
			if err != nil {
				msg := fmt.Sprintf("Unexpected format of .bash_history while parsing timestamp : %v", err)
				return nil, status.Error(codes.Internal, msg)
			}

			e.Time = time.Unix(timestamp, 0).Format("2006-01-02 15:04:05")
			e.Cmd = lines[i+1]
			e.User = u.Name

			// add to the response
			r.Entries = append(r.Entries, e)
			// continue to parse the next entry
			i += 2
		} else {	// parse a one-line entry
			e := &pb.BashHistoryEntry{}
			e.Cmd = lines[i]
			e.Time = "unknown"
			e.User = u.Name
			r.Entries = append(r.Entries, e)
			i += 1
		}
	}
	return r, nil
}

/*
   🐢 : In order for the bash to record the time of each command, do

   echo 'HISTTIMEFORMAT="%F %T "' >> /etc/environment
*/
func GetBashHistory0() (*pb.GetBashHistoryRpl, error) {
	// 1. get all the users
	users, err := GetUsers0()
	if err != nil {
		return nil, err
	}

	// 2. try add his or her entries
	r := &pb.GetBashHistoryRpl{}
	for _, u := range users.Users {

		r, err = tryAddHisOrHerEntries(u, r)
		if err != nil {
			return nil, err
		}
	}

	// 3. return
	return r, nil
}

func (s *server) GetBashHistory(ctx context.Context, in *pb.Empty) (*pb.GetBashHistoryRpl, error) {
	// 🦜 : Needs root to read other users' .bash_history, so we need to mock it
	if dryRun {
		return &pb.GetBashHistoryRpl{
			Entries: []*pb.BashHistoryEntry{
				{Cmd: "sudo --help", Time: "2021-01-01 12:00:00", User: "me",},
				{Cmd: "sudo -u keystone bash -c 'echo hi'", Time: "2021-01-01 12:00:00", User: "me",},
				{Cmd: "go test .", Time: "unknown", User: "me",},
				{Cmd: "ls /etc/environment", Time: "2021-01-01 12:00:00", User: "me",},
				{Cmd: "less ~/.bash_history", Time: "2021-01-01 12:00:00", User: "me",},
				{Cmd: "sudo --help", Time: "2021-01-01 12:00:00", User: "aaa",},
				{Time : "2024-01-15 19:05", Cmd: "git commit -m 'Start adding bash history'", User: "me"},
				{Time : "2024-01-15 19:06", Cmd: "gpsh main", User: "me"},
				{Time : "2024-01-16 10:30", Cmd: "got", User: "me"},
				{Time : "2024-01-16 10:30", Cmd: "l", User: "me"},
				{Time : "2024-01-16 10:30", Cmd: "cd go-agent", User: "me"},
				{Time : "2024-01-16 10:30", Cmd: "ls", User: "me"},
				{Time : "2024-01-16 10:31", Cmd: "cat hi.zsh", User: "me"},
				{Time : "2024-01-16 10:31", Cmd: "go run . --safety=2 --mockMore=true\n", User: "me"},
				{Time : "2024-01-16 10:31", Cmd: "ls", User: "me"},
				{Time : "2024-01-16 10:31", Cmd: "emacs src/main/java/aaa/M.java &", User: "me"},
				{Time : "2024-01-16 10:32", Cmd: "idea .", User: "me"},
				{Time : "2024-01-16 10:35", Cmd: "cd ..", User: "me"},
				{Time : "2024-01-16 10:35", Cmd: "./mvnw test", User: "me"},
				{Time : "2024-01-16 10:37", Cmd: "l", User: "me"},
				{Time : "2024-01-16 10:37", Cmd: ". ./s.sh", User: "me"},
				{Time : "2024-01-16 10:39", Cmd: "history ", User: "me"},
				{Time : "2024-01-16 10:39", Cmd: "history --help", User: "me"},
				{Time : "2024-01-16 10:39", Cmd: "history -h", User: "me"},
				{Time : "2024-01-16 10:39", Cmd: "history -t", User: "me"},
				{Time : "2024-01-16 10:39", Cmd: "history help", User: "me"},
				{Time : "2024-01-16 10:39", Cmd: "clear", User: "me"},
				{Time : "2024-01-16 10:39", Cmd: "help history", User: "me"},
				{Time : "2024-01-16 10:39", Cmd: "history -h", User: "me"},
				{Time : "2024-01-16 10:39", Cmd: "ddg zsh history", User: "me"},
				{Time : "2024-01-16 10:39", Cmd: "cfw", User: "me"},
				{Time : "2024-01-16 10:40", Cmd: "fc", User: "me"},
				{Time : "2024-01-16 10:40", Cmd: "cfw", User: "me"},
				{Time : "2024-01-16 10:41", Cmd: "fc -l", User: "me"},
				{Time : "2024-01-16 10:41", Cmd: "fc --help", User: "me"},
				{Time : "2024-01-16 10:41", Cmd: "hc -h", User: "me"},
				{Time : "2024-01-16 10:41", Cmd: "man history", User: "me"},
				{Time : "2024-01-16 10:41", Cmd: "clear", User: "me"},
				{Time : "2024-01-16 11:01", Cmd: "history", User: "me"},
				{Time : "2024-01-16 11:02", Cmd: "history", User: "me"},
				{Time : "2024-01-16 11:02", Cmd: "cat ~/.zsh_aliases", User: "me"},
				{Time : "2024-01-16 11:02", Cmd: "cat ~/.zsh_history", User: "me"},
				{Time : "2024-01-16 11:02", Cmd: "clear", User: "me"},
				{Time : "2024-01-16 11:02", Cmd: "history", User: "me"},
				{Time : "2024-01-16 11:03", Cmd: "history -n 5", User: "me"},
				{Time : "2024-01-16 11:03", Cmd: "ddg zsh history timestamp", User: "me"},
				{Time : "2024-01-16 11:03", Cmd: "history -E", User: "me"},
				{Time : "2024-01-16 11:03", Cmd: "history -i", User: "me"},
				{Time : "2024-01-16 11:03", Cmd: "clear", User: "me"},
				{Time : "2024-01-16 11:03", Cmd: "history -E", User: "me"},
				{Time : "2024-01-16 11:04", Cmd: "clear", User: "me"},
			},
		}, nil
	}

	return GetBashHistory0()
}

func getBootTime0() string {
	// 服务器开机时间 uptime -s
	var execBootTimeCmd = "uptime -s"
	bootTimeCmd := exec.Command("bash", "-c", execBootTimeCmd)
	bootTimeOutput, _ := bootTimeCmd.Output()

	// 解析输出，获取当前正在运行的应用
	BootTimeLines := strings.Split(string(bootTimeOutput), "\n")
	for _, v := range BootTimeLines {
		if len(v) == 0 {
			continue
		}
		return v
	}
	return ""
}

func check_port(port_s  string) bool{
	port, err := strconv.Atoi(port_s)
	if err != nil {
		return false
	}
	return port >= 1 && port <= 65535
}

func addPortRangeAndConvertMaybe(s *pb.InboundRule) (string, error) {

	if s.Type == pb.InboundRuleType_icmp || s.Type == pb.InboundRuleType_all {
		return "", nil
	}

	if s.PortRange == "" {
		return "", nil
	}

	if s.PortRange == "all" {
		return "", nil
	}

	/*
	   🐢: check if the port range is valid, if must be something like '80'
	   or '8000:8002'

	   🦜 : Time to use regex

	   🐢 : Emm. The first thing to note is that the port range can be `port[:port]`
	 */
	r := regexp.MustCompile(`^(\d+)(:\d+)?$`)
	if !r.MatchString(s.PortRange) {
		msg := fmt.Sprintf("端口范围格式错误: %s", s.PortRange)
		return "", status.Error(codes.InvalidArgument, msg)
	}


	// port should be in 1 ～ 65535
	if strings.Contains(s.PortRange, ":") {
		ports_s := strings.Split(s.PortRange, ":")
		// if len(ports_s) != 2 {
		// 	return "", status.Error(codes.InvalidArgument, "端口范围错误: " + s.PortRange)
		// }
		if !check_port(ports_s[0]) || !check_port(ports_s[1]) {
			return "", status.Error(codes.InvalidArgument, "端口范围错误: " + s.PortRange + " ,需要在1～65535之间")
		}
	}else{
		if !check_port(s.PortRange) {
			return "", status.Error(codes.InvalidArgument, "端口范围错误: " + s.PortRange + " ,需要在1～65535之间")
		}
	}

	return fmt.Sprintf(" --destination-port %s", s.PortRange), nil
}

func addSourceMaybe(s *pb.InboundRule) (string, error) {
	if s.Source == "" {
		return "", nil
	}

	// check format: address[/mask][,...]
	if s.Ipv6 {
		r := regexp.MustCompile(`^([0-9a-fA-F:]+)(\/\d+)?$`)
		if !r.MatchString(s.Source) {
			msg := fmt.Sprintf("ipv6源地址格式错误: %s", s.Source)
			return "", status.Error(codes.InvalidArgument, msg)
		}
	}else{
		// r := regexp.MustCompile(`^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.){3}(25[0-5]|(2[0-4]|1\d|[1-9]|)\d)$`)

		// if !r.MatchString(s.Source) {
		// 	msg := fmt.Sprintf("ipv4源地址格式错误: %s", s.Source)
		// 	return "", status.Error(codes.InvalidArgument, msg)
		// }

		// <2024-02-28 Wed>: Update, use split and parse

		bad := func() error {
			msg := fmt.Sprintf("ipv4源地址格式错误: %s", s.Source)
			return status.Error(codes.InvalidArgument, msg)
		}

		l := strings.Split(s.Source, ".")
		if len(l) != 4 {
			return "", bad()
		}

		checkV4Digit := func(s string) error {
			x, err := strconv.Atoi(s)
			if err != nil {
				return bad()
			}

			// check
			if x < 0 || x > 255 {
				return bad()
			}
			return nil
		}

		for i:=0;i<3;i++ {
			if err := checkV4Digit(l[i]); err != nil {
				return "", err
			}
		}

		// the last can be something like '1' or '1/24'
		if strings.Contains(l[3], "/") {
			l2 := strings.Split(l[3], "/")
			if len(l2) != 2 {
				return "", bad()
			}

			if err := checkV4Digit(l2[0]); err != nil {
				return "", err
			}

			// check the mask
			mask, err := strconv.Atoi(l2[1])
			if err != nil {
				return "" , bad()
			}

			if mask < 0 || mask > 32 {
				return "" , bad()
			}
		}else{
			if err := checkV4Digit(l[3]); err != nil {
				return "", err
			}
		}
	}

	// 🐢: check if the source is valid, if must be something like '
	return fmt.Sprintf(" --source %s", s.Source), nil
}

func toCmdString(s *pb.InboundRule) (string,error) {

	// 🐢: form the iptables command string
	c := "--table filter --insert ChainAaa"

	if s.Ipv6 {
		c = "ip6tables " + c
	}else{
		c = "iptables " + c
	}
	// 1. set source
	// --------------------------------------------------
	s1 , err := addSourceMaybe(s)
	if err != nil {
		return "", err
	}
	c += s1

	// 2. set based on different types
	// --------------------------------------------------
	if s.Type == pb.InboundRuleType_icmp {
		c += " --protocol icmp"
	}

	if s.Type == pb.InboundRuleType_tcp {
		c += " --protocol tcp"
	}

	if s.Type == pb.InboundRuleType_udp {
		c += " --protocol udp"
	}

	// 3. add ports
	// --------------------------------------------------
	s1 , err = addPortRangeAndConvertMaybe(s)
	if err != nil {
		return "", err
	}

	c += s1
	c +=" --jump ACCEPT"
	return c,nil
}

func (s *server) GetBootTime(ctx context.Context, in *pb.Empty) (*pb.HelloReply, error) {
	r := &pb.HelloReply{}
	r.Msg = getBootTime0()
	return r, nil
}

func (s *server) SetInboundRules(ctx context.Context, in *pb.SetInboundRulesReq) (*pb.HelloReply, error) {
	// 0. convert the rule to string one-by-one
	// --------------------------------------------------
	var cmds []string
	for _, rule := range in.Rules {
		cmd , err := toCmdString(rule)
		if err != nil {
			msg := fmt.Sprintf("转换规则错误: %v", err)
			return nil, status.Error(codes.InvalidArgument, msg)
			// do nothing
		}
		log.Printf("🦜 : about to exec: " + Blue("%s"), cmd)
		cmds = append(cmds, cmd)
	}

	if !dryRun {
		// 🦜 : 1. remake the table by calling `iptable_clean.sh` and `iptable_addBlackhole.sh`
		// --------------------------------------------------
		r, err := invokeScript("iptable_clean.sh")
		log.Println("iptable_clean.sh : " + r)
		if err != nil {
			msg := fmt.Sprintf("执行脚本 iptable_clean 错误: %v", err)
			return nil, status.Error(codes.Internal, msg)
		}
		r, err = invokeScript("iptable_addBlackhole.sh")
		log.Println("iptable_addBlackhole.sh :" + r)
		if err != nil {
			msg := fmt.Sprintf("执行脚本 iptable_addBlackhole 错误: %v", err)
			return nil, status.Error(codes.Internal, msg)
		}

		// 2. add the rules one-by-one
		// --------------------------------------------------
		for _, cmd := range cmds {
			c := exec.Command("bash", "-c", cmd)
			_ , err := c.Output()
			if err != nil {
				msg := fmt.Sprintf("执行命令错误: %v", err)
				return nil, status.Error(codes.Internal, msg)
			}
		}
	}

	return &pb.HelloReply{Msg: "ok"}, nil
}

func invokeScript(script string) (string, error) {
	c := fmt.Sprintf("%s/script/%s", cwd, script)

	cmd := exec.Command("bash", "-c", c)
	output, err := cmd.Output()
	if err != nil {
		msg := fmt.Sprintf("执行脚本错误: %v", err)
		return "", status.Error(codes.Internal, msg)
	}
	return string(output), nil
}

// [2024-01-26] 🦜 this is deprecated by the use of gopsutil
// func parseLineIntoProcessInfo(l string) (*pb.ProcessInfo, error){
// }

func (s *server) GetProcessInfo(ctx context.Context, in *pb.Empty) (*pb.GetProcessInfoRpl, error) {
	/*
	   1. [2024-01-24] update to use "github.com/shirou/gopsutil/v3/process"
	 */
	r := &pb.GetProcessInfoRpl{}
	all, err := process.Processes()
	if err != nil {
		msg := fmt.Sprintf("获取进程信息错误: %v", err)
		return nil, status.Error(codes.Internal, msg)
	}

	r.Processes = make([]*pb.ProcessInfo, 0, len(all))
	// go -> pb
	for _, p := range all {
		need_sudo := false
		pi := &pb.ProcessInfo{}
		pi.User, err = p.Username()
		if err != nil {
			log.Printf("获取进程信息错误: %v", err)
			continue
		}
		pi.Name, err = p.Name()
		if err != nil {
			log.Printf("获取进程Name错误: %v", err)
			continue
		}
		pi.Pid = p.Pid
		pi.Ppid, err = p.Ppid()
		if err != nil {
			log.Printf("获取进程Ppid错误: %v", err)
			// continue
		}

		pi.Cwd, err = p.Cwd()
		if err != nil {
			need_sudo = true
			// log.Printf("获取进程cwd错误: %v", err)
			pi.Cwd = ""
		}

		pi.CpuPercent , err = p.CPUPercent()
		if err != nil {
			log.Printf("获取进程cpu percent错误: %v", err)
			// continue
		}

		pi.MemPercent , err = p.MemoryPercent()
		if err != nil {
			log.Printf("获取进程mem percent错误: %v", err)
		}

		// get ioCounters
		if !need_sudo {
			ioCounters, err := p.IOCounters()
			if err != nil {
				// log.Printf("获取进程ioCounters错误: %v", err)
			}else{
				pi.IoCounters = &pb.ProcessIOCountersStat{
					ReadCount: ioCounters.ReadCount,
					ReadBytes: ioCounters.ReadBytes,
					WriteCount: ioCounters.WriteCount,
					WriteBytes: ioCounters.WriteBytes,
				}
			}
		}

		var n_ms int64
		n_ms, err = p.CreateTime() // number of ms since epoch
		if err != nil {
			log.Printf("获取进程createTime错误: %v", err)
		}
		pi.CreateTime = time.Unix(n_ms/1000, 0).Format("2006-01-02 15:04:05")

		pi.IsRunning, err = p.IsRunning()
		if err != nil {
			log.Printf("获取进程isRunning错误: %v", err)
		}

		pi.IsForeground, err = p.Foreground()
		if err != nil {
			log.Printf("获取进程isForeground错误: %v", err)
		}

		pi.Cmdline = []string{}
		if !need_sudo {
			pi.Cmdline, err = p.CmdlineSlice()
			if err != nil {
				log.Printf("获取进程信息错误: %v", err)
				// return nil, status.Error(codes.Internal, msg)
			}
		}


		pi.ExePath = ""
		pi.Hash = ""

		// --------------------------------------------------


		// append to the result
		r.Processes = append(r.Processes, pi)
	}


	/*
	   2. if we have sudo,
	   get the hash by calling `sudo sha256sum /proc/<pid>/exe`
	     such as

	   sudo sha256sum /proc/1009/exe | cut --delimiter=' ' --fields=1
	*/
	if !dryRun {
		for _, pi := range r.Processes {
			// // 🦜 : 2.1 form the command

			// // follow the link to get the executable path
			// cmd = fmt.Sprintf("readlink /proc/%d/exe", p.Pid)
			// c = exec.Command("bash", "-c", cmd)
			// output, err = c.Output()
			// if err == nil {
			// 	p.ExePath = strings.Trim(string(output), "\n")
			// }

			/* [2024-01-26] Update: 🦜 : we switch to use the psutil pkg.
			 */
			p , err := process.NewProcess(pi.Pid)
			if err != nil {
				log.Printf("获取进程信息错误: %v", err)
				continue
			}
			pi.ExePath, err = p.Exe()
			if err != nil {
				log.Printf("获取进程exe path错误: %v", err)
				continue
			}
			cmd := fmt.Sprintf("sha256sum /proc/%d/exe | cut --delimiter=' ' --fields=1", p.Pid)

			// 🦜 : 2.2 exec the command
			c := exec.Command("bash", "-c", cmd)
			output, err := c.Output()
			if err == nil {
				// 🦜 : 2.3 fill in the hash
				pi.Hash = strings.Trim(string(output), "\n")
			}
		}
	}

	return r , nil
}

/*
  rpc StreamOneoffProcessInfo (Empty) returns (stream OneoffProcessInfo) {}
   message OneoffProcessInfo {
  string time = 1;
  string comm = 2;
  int32 pid = 3;
  string ppid = 4;
  string argv = 5;
  int32 retVal = 6;
  }
*/

func (s *server) StreamOneoffProcessInfo(in *pb.Empty, stream pb.Greeter_StreamOneoffProcessInfoServer) error {
	/*
	   🦜 : For now, let's not do any mock..It doesn't do anything dangerous
	   such as adding filewall rules... Also, now it's only designed to work
	   with one listener.

	*/

	// 1. make output channel for the OneoffProcessInfo
	ear := make(chan OneoffProcessInfo)
	stopButton := make(chan struct{})
	go ListenToOneOff(ear, stopButton)
	// the consumer 🦜: pass to the stream
	for {
		oi := <-ear
		// fmt.Printf("🦜 : Learned %v\n", oi)
		r := &pb.OneoffProcessInfo{
			UnixMilli: oi.UnixMilli,
			Comm: oi.Comm,
			Pid: oi.Pid,
			Ppid: oi.Ppid,
			Uid: oi.Uid,
			Argv: oi.Argv,
			RetVal: oi.RetVal,
		}
		if err := stream.Send(r); err != nil {
			log.Printf("Error sending to the stream: %v", err)
			break
		}
	}
	stopButton <- struct{}{}
	log.Println("👋 : StreamOneoffProcessInfo done.")
	return nil
}



func Start(mmyIp string, port int, ssafety int, mmockMore bool) {
	myIp = mmyIp
	safety = ssafety
	dryRun = (safety == 2)
	mockMore = mmockMore

	cwd, err := os.Getwd()
	log.Println("⚙️ :cwd: " + Green(cwd))
	if err != nil {		// unlikely
		msg := fmt.Sprintf("获取当前目录错误: %v", err)
		log.Fatal(msg)
	}

	// --------------------------------------------------
	// 0. prepare the iptables
	if !dryRun {
		r, err := invokeScript("iptable_create.sh")
		log.Printf("iptable_create.sh : %s\n",r)
		if err != nil {
			log.Fatalf("执行脚本 iptable_create 错误 %v", err)
		}
		r, err = invokeScript("iptable_addBlackhole.sh")
		if err != nil {
			log.Fatalf("执行脚本 iptable_addBlackhole 错误 %v", err)
		}
	}


	// --------------------------------------------------
	// 1. Listen on port

	lis, err := net.Listen("tcp", fmt.Sprintf(":%d",port))
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	reflection.Register(s)
	log.Printf("server listening at %v", lis.Addr())


	// --------------------------------------------------
	// 2. Start listening to the 

	// --------------------------------------------------
	// 2. Start the server in a goroutine
	go func() {
		if err := s.Serve(lis); err != nil {
			log.Fatalf("failed to serve: %v", err)
		}
	}()
	defer stop(s)		// 🐢 if any thing bad (kinda finally)

	// --------------------------------------------------
	// 3. wait for an input
	fmt.Println("Press 'Enter' to exit...")
	scn := bufio.NewScanner(os.Stdin)
	scn.Scan()
	log.Println("👋 bye.")
	// 4. 🦜 : stop() should be called here.
}


func stop(s *grpc.Server) {
	// 🐢 : Graceful stop
	s.GracefulStop()
	log.Println("\t 🚮️ Server gracefully shut down.")

	// --------------------------------------------------
	// -0. remove the iptables
	if !dryRun {
		r, err := invokeScript("iptable_remove.sh")
		if err != nil {
			log.Fatalf("执行脚本 iptable_clean 错误 %v", err)
		}
		log.Println(r)
	}
}
